﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading;
using UtilZ.Dotnet.Ex.ZLog;

namespace UtilZ.Dotnet.Ex.Base
{
    /// <summary>
    /// 程序集管理辅助类
    /// </summary>
    public static class AssemblyEx
    {
        private readonly static ConcurrentDictionary<string, Assembly> _targetAssemblyDic = new ConcurrentDictionary<string, Assembly>();

        /// <summary>
        /// 是否内部加载数据库访问组件
        /// </summary>
        private static bool _enable = false;

        /// <summary>
        /// 获取或设置是否内部加载数据库访问组件,true:内部加载,false:外部加载
        /// </summary>
        public static bool Enable
        {
            get
            {
                return _enable;
            }
            set
            {
                if (_enable == value)
                {
                    return;
                }

                _enable = value;
#if CORE
                if (_enable)
                {
                    System.Runtime.Loader.AssemblyLoadContext.Default.Resolving += ManualLoadAssembly2;
                }
                else
                {
                    System.Runtime.Loader.AssemblyLoadContext.Default.Resolving -= ManualLoadAssembly2;
                }
#else

                if (_enable)
                {
                    AppDomain.CurrentDomain.AssemblyResolve += ManualLoadAssembly1;
                }
                else
                {
                    AppDomain.CurrentDomain.AssemblyResolve -= ManualLoadAssembly1;
                }
#endif
            }
        }



        /// <summary>
        /// 默认查找范围[AllDirectories:主程序及所有了子目录;TopDirectoryOnly:仅RequestingAssembly所在目录]
        /// </summary>
        private static SearchOption _defaultFindOption = SearchOption.AllDirectories;
        /// <summary>
        /// 获取或设置默认查找范围[AllDirectories:主程序及所有了子目录;TopDirectoryOnly:仅RequestingAssembly所在目录]
        /// </summary>
        public static SearchOption DefaultFindOption
        {
            get
            {
                return _defaultFindOption;
            }
            set
            {
                _defaultFindOption = value;
            }
        }


        private static int _idIndex = 1;
        /// <summary>
        /// 手动加载程序集委托列表[key:委托标识;value:委托]
        /// </summary>
        private readonly static ConcurrentDictionary<int, AssemblyResolveArgs> _manualLoadAssemblyFuncDic = new ConcurrentDictionary<int, AssemblyResolveArgs>();

        /// <summary>
        /// 加载数组库访问程序集
        /// </summary>
        /// <param name="sender">sender</param>
        /// <param name="args">需要的程序集名称信息参数</param>
        private static System.Reflection.Assembly ManualLoadAssembly1(object sender, ResolveEventArgs args)
        {
            AssemblyName assemblyName = new AssemblyName(args.Name);
            Assembly targetAssembly = InnerManualLoadAssembly(assemblyName);
            if (targetAssembly != null)
            {
                return targetAssembly;
            }

            var assemblyResolveArgsArr = _manualLoadAssemblyFuncDic.Values.ToArray();
            foreach (AssemblyResolveArgs assemblyResolveArgs in assemblyResolveArgsArr)
            {
                assemblyResolveArgs.Excute(assemblyName);
                if (assemblyResolveArgs.Succes)
                {
                    return Assembly.LoadFrom(assemblyResolveArgs.TargetAssemblyFilePath);
                }
            }

            return null;
        }


#if CORE
        private static Assembly ManualLoadAssembly2(System.Runtime.Loader.AssemblyLoadContext context, AssemblyName name)
        {
            Assembly targetAssembly = InnerManualLoadAssembly(name);
            if (targetAssembly != null)
            {
                return targetAssembly;
            }

            var assemblyResolveArgsArr = _manualLoadAssemblyFuncDic.Values.ToArray();
            foreach (AssemblyResolveArgs assemblyResolveArgs in assemblyResolveArgsArr)
            {
                assemblyResolveArgs.Excute(name);
                if (assemblyResolveArgs.Succes)
                {
                    return context.LoadFromAssemblyPath(assemblyResolveArgs.TargetAssemblyFilePath);
                }
            }

            return null;
        }
#endif

        /// <summary>
        /// 加载数组库访问程序集
        /// </summary>
        /// <param name="assemblyName">需要的程序集全名称</param>
        /// <returns>获取成功的程序集,获取失败返回null</returns>
        private static System.Reflection.Assembly InnerManualLoadAssembly(AssemblyName assemblyName)
        {
            Assembly targetAssembly;
            //验证是否存在已查找过的集合中,如果已存在则直接取值返回
            if (_targetAssemblyDic.TryGetValue(assemblyName.FullName, out targetAssembly))
            {
                return targetAssembly;
            }

            targetAssembly = FindAssembly(assemblyName);
            if (targetAssembly != null)
            {
                return targetAssembly;
            }

            //在主程序目录以及所有子目录中查找目标程序集
            string[] filePathArr = System.IO.Directory.GetFiles(PathEx.GetEntryAssemblyDirectory(), $"{assemblyName.Name}.dll", _defaultFindOption);
            if (filePathArr.Length == 0)
            {
                return null;
            }

            AssemblyName tempAssemblyName;
            for (int i = 0; i < filePathArr.Length; i++)
            {
                try
                {
                    tempAssemblyName = AssemblyName.GetAssemblyName(filePathArr[i]);
                }
                catch
                {
                    continue;
                }

                if (CheckTargetAssembly(assemblyName, tempAssemblyName))
                {
                    targetAssembly = LoadAssemblyFromPath(filePathArr[i]);
                    if (targetAssembly != null)
                    {
                        return targetAssembly;
                    }
                }
            }

            return null;
        }


        /// <summary>
        /// 从指定程序集文件路径加载程序集,加载成功时返回加载成功的程序集,失败时返回null
        /// </summary>
        /// <param name="filePath">要加载的程序集文件路径</param>
        /// <returns>加载成功时返回加载成功的程序集,失败时返回null</returns>
        public static Assembly LoadAssemblyFromPath(string filePath)
        {
            try
            {
#if CORE
                return System.Runtime.Loader.AssemblyLoadContext.Default.LoadFromAssemblyPath(filePath);
#else
                return Assembly.LoadFrom(filePath);
#endif
            }
            catch (Exception ex)
            {
                ZLoger.Error(ex);
                return null;
            }
        }

        /// <summary>
        /// 添加一个手动加载程序集委托,并返回该委托对应的id
        /// </summary>
        /// <param name="data">数据</param>
        /// <param name="func">委托,找着了返回目标程序集,未找着返回null,第一个参数为key,第二个参数起为AppDomain.CurrentDomain.AssemblyResolve参数</param>
        /// <returns>委托对应的id</returns>
        public static int AddManualLoadAssemblyFunc(object data, Action<AssemblyResolveArgs> func)
        {
            if (func == null)
            {
                throw new ArgumentNullException(nameof(func));
            }

            int id = Interlocked.Increment(ref _idIndex);
            while (true)
            {
                if (_manualLoadAssemblyFuncDic.TryAdd(id, new AssemblyResolveArgs(id, data, func)))
                {
                    break;
                }
                else
                {
                    id = Interlocked.Increment(ref _idIndex);
                }
            }

            return id;
        }

        /// <summary>
        /// 移除一个手动加载程序集委托
        /// </summary>
        /// <param name="id">注册ID</param>
        public static void RemoveManualLoadAssemblyFunc(int id)
        {
            AssemblyResolveArgs args;
            _manualLoadAssemblyFuncDic.TryRemove(id, out args);
        }

        /// <summary>
        /// 清空手动加载程序集委托
        /// </summary>
        public static void ClearManualLoadAssemblyFunc()
        {
            _manualLoadAssemblyFuncDic.Clear();
        }

        /// <summary>
        /// 从当前应用程序域中查找指定名称的程序集[找到返回目标程序集,没找到返回null]
        /// </summary>
        /// <param name="name">程序集名称或全名</param>
        /// <returns>找到返回目标程序集,没找到返回null</returns>
        public static Assembly FindAssembly(string name)
        {
#if CORE
            IEnumerable<Assembly> assemblys = System.Runtime.Loader.AssemblyLoadContext.Default.Assemblies;
#else
            IEnumerable<Assembly> assemblys = AppDomain.CurrentDomain.GetAssemblies();
#endif

            //return (from assembly in assemblys where string.Equals(name, assembly.FullName) || string.Equals(name, assembly.GetName().Name) select assembly).FirstOrDefault();
            Assembly targetAssembly = (from assembly in assemblys where string.Equals(name, assembly.FullName) select assembly).FirstOrDefault();
            if (targetAssembly != null)
            {
                return targetAssembly;
            }

            targetAssembly = (from assembly in assemblys where string.Equals(name, assembly.GetName().Name) select assembly).FirstOrDefault();
            return targetAssembly;
        }

        /// <summary>
        /// 从当前应用程序域中查找指定名称的程序集[找到返回目标程序集,没找到返回null]
        /// </summary>
        /// <param name="assemblyName">程序集名称或全名</param>
        /// <returns>找到返回目标程序集,没找到返回null</returns>
        public static Assembly FindAssembly(AssemblyName assemblyName)
        {
#if CORE
            IEnumerable<Assembly> assemblys = System.Runtime.Loader.AssemblyLoadContext.Default.Assemblies;
#else
            IEnumerable<Assembly> assemblys = AppDomain.CurrentDomain.GetAssemblies();
#endif

            return assemblys.FirstOrDefault((t) =>
            {
                return CheckTargetAssembly(assemblyName, t.GetName());
            });
        }

        /// <summary>
        /// 检查目标程序集名称是否匹配需要的程序集名称[匹配返回true,不匹配返回false]
        /// </summary>
        /// <param name="requestingAssemblyName">需要的程序集名称</param>
        /// <param name="targetAssemblyName">目标程序集名称</param>
        /// <returns>匹配返回true,不匹配返回false</returns>
        public static bool CheckTargetAssembly(AssemblyName requestingAssemblyName, AssemblyName targetAssemblyName)
        {
            return string.Equals(requestingAssemblyName.Name, targetAssemblyName.Name) &&
                targetAssemblyName.Version >= requestingAssemblyName.Version;
        }

        /// <summary>
        /// 添加一个程序集
        /// </summary>
        /// <param name="assembly">程序集</param>
        public static void AddAssembly(System.Reflection.Assembly assembly)
        {
            _targetAssemblyDic[assembly.FullName] = assembly;
            _targetAssemblyDic[assembly.GetName().Name] = assembly;
        }

        /// <summary>
        /// 添加一个程序集集合
        /// </summary>
        /// <param name="assemblies">程序集集合</param>
        public static void AddAssembly(IEnumerable<System.Reflection.Assembly> assemblies)
        {
            foreach (var assembly in assemblies)
            {
                AddAssembly(assembly);
            }
        }

        /// <summary>
        /// 获取目标程序集集合
        /// </summary>
        public static Assembly[] TargetAssemblys
        {
            get
            {
                return _targetAssemblyDic.Values.Distinct().ToArray();
            }
        }

        /// <summary>
        /// 目标程序集中是否包含指定名称的程序集[存在返回:true,否则返回false]
        /// </summary>
        /// <param name="assemblyName">程序集名称</param>
        /// <returns>存在返回:true,否则返回false</returns>
        public static bool ContainsAssembly(string assemblyName)
        {
            return _targetAssemblyDic.ContainsKey(assemblyName);
        }

        /// <summary>
        /// 移除目标程序集
        /// </summary>
        /// <param name="assembly"></param>
        public static void RemoveAssembly(Assembly assembly)
        {
            Assembly assembly2;
            _targetAssemblyDic.TryRemove(assembly.FullName, out assembly2);
            _targetAssemblyDic.TryRemove(assembly.GetName().Name, out assembly2);
        }

        /// <summary>
        /// 清空目标程序集集合
        /// </summary>
        public static void ClearAssembly()
        {
            _targetAssemblyDic.Clear();
        }



        /// <summary>
        /// 获取程序集名称[获取失败或非.net程序集,则返回null]
        /// </summary>
        /// <param name="assemblyPath">程序集路径</param>
        /// <returns>程序集名称</returns>
        public static string GetAssemblyName(string assemblyPath)
        {
            try
            {
                if (System.IO.File.Exists(assemblyPath))
                {
                    return AssemblyName.GetAssemblyName(assemblyPath).FullName;
                }
                else
                {
                    return null;
                }
            }
            catch
            {
                return null;
            }
        }





        /// <summary>
        /// 判断dll文件是64位还是32位[true:64位;false:32位;.NET AnyCpu程序集判断为32位]
        /// </summary>
        /// <param name="dllFilePath">dll文件路径</param>
        /// <returns>[true:64位;false:32位;.NET AnyCpu程序集判断为32位]</returns>
        public static bool IsX64OrX86(string dllFilePath)
        {
            using (System.IO.FileStream fStream = new System.IO.FileStream(dllFilePath, System.IO.FileMode.Open, System.IO.FileAccess.Read))
            {
                System.IO.BinaryReader bReader = new System.IO.BinaryReader(fStream);
                if (bReader.ReadUInt16() != 23117) //check the MZ signature
                {
                    throw new Exception("不识别的dll");
                }

                fStream.Seek(0x3A, System.IO.SeekOrigin.Current); //seek to e_lfanew.
                fStream.Seek(bReader.ReadUInt32(), System.IO.SeekOrigin.Begin); //seek to the start of the NT header.
                if (bReader.ReadUInt32() != 17744) //check the PE\0\0 signature.
                {
                    throw new Exception("不识别的dll");
                }

                fStream.Seek(20, System.IO.SeekOrigin.Current); //seek past the file header,
                ushort architecture = bReader.ReadUInt16(); //read the magic number of the optional header.
                //523 64位    267 32位
                switch (architecture)
                {
                    case 523:
                        return true;
                    case 267:
                        return false;
                    default:
                        throw new Exception("不识别的dll类型");
                }
            }
        }




        /*
        /// <summary>
        /// 编码程序集[返回编码结果]
        /// </summary>
        /// <param name="sourceFilePaths">源码文件路径数组</param>
        /// <param name="referencedAssemblyFilePaths">所有引用程序集路径数组</param>
        /// <param name="outAssemblyFilePath">输出程序集路径</param>
        /// <returns>CompilerResults</returns>
        public static CompilerResults CompileAssembly(string[] sourceFilePaths, string[] referencedAssemblyFilePaths, string outAssemblyFilePath)
        {
            if (sourceFilePaths == null || sourceFilePaths.Length == 0)
            {
                throw new ArgumentNullException("sourceFilePaths");
            }

            string sourceFileExtension = System.IO.Path.GetExtension(sourceFilePaths[0].ToUpper(System.Globalization.CultureInfo.InvariantCulture));
            string extension;
            foreach (var sourceFilePath in sourceFilePaths)
            {
                extension = System.IO.Path.GetExtension(sourceFilePaths[0].ToUpper(System.Globalization.CultureInfo.InvariantCulture));
                if (!string.Equals(sourceFileExtension, extension))
                {
                    throw new ArgumentException("源文件扩展名必须一致");
                }
            }

            CodeDomProvider provider;
            switch (sourceFileExtension)
            {
                case ".CS":
                    provider = CodeDomProvider.CreateProvider("CSharp");
                    break;
                case ".VB":
                    provider = CodeDomProvider.CreateProvider("VisualBasic");
                    break;
                default:
                    throw new ArgumentException("源文件扩展名必须为.cs或.vb");
            }

            var cp = new CompilerParameters();
            cp.GenerateExecutable = false;
            cp.OutputAssembly = outAssemblyFilePath;
            cp.GenerateInMemory = false;
            cp.TreatWarningsAsErrors = false;
            cp.ReferencedAssemblies.AddRange(referencedAssemblyFilePaths);
            CompilerResults cr = provider.CompileAssemblyFromFile(cp, sourceFilePaths);
            return cr;
        }
        */




        /// <summary>
        /// 获取程序集GUID
        /// </summary>
        /// <param name="assembly">Assembly</param>
        /// <returns>程序集GUID</returns>
        public static string GetAssemblyGUID(Assembly assembly)
        {
            if (assembly == null)
            {
                throw new ArgumentNullException(nameof(assembly));
            }

            return assembly.GetCustomAttributes(true).Where(t => t is GuidAttribute)?.Cast<GuidAttribute>()?.Select(t => t.Value).FirstOrDefault();
        }
    }

    /// <summary>
    /// 程序集解决参数
    /// </summary>
    public class AssemblyResolveArgs : EventArgs
    {
        /// <summary>
        /// 注册回调ID
        /// </summary>
        public int ID { get; private set; }

        /// <summary>
        /// 关联数据
        /// </summary>
        public object Data { get; private set; }

        /// <summary>
        /// 所需目标程序集全名
        /// </summary>
        public AssemblyName TargetaAsemblyName { get; private set; }

        /// <summary>
        /// 获取成功时程序集路径
        /// </summary>
        public string TargetAssemblyFilePath { get; set; }

        /// <summary>
        /// 获取成功标识,成功时设置为true
        /// </summary>
        public bool Succes { get; set; }


        private readonly Action<AssemblyResolveArgs> _func;

        internal AssemblyResolveArgs(int id, object data, Action<AssemblyResolveArgs> func)
        {
            this.ID = id;
            this.Data = data;
            this._func = func;
        }

        internal void Excute(AssemblyName assemblyName)
        {
            this.TargetaAsemblyName = assemblyName;
            this.Succes = false;
            this._func(this);
        }

    }
}
